
// This file is part of Module Proxy.

// Module Proxy is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Module Proxy is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Module Proxy.  If not, see <https://www.gnu.org/licenses/>.


//         Copyright (C) 2021 - 2030  关中麦客  
//         All rights reserved
//
//         mod_html.rs
//         静态模块，针对.html .css .js .png .jpg等静态文件
//
//         created by 关中麦客 1036038462@qq.com

use std::path::Path;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio_util::codec::{BytesCodec, FramedRead};
use hyper::{Body, Response, StatusCode, header, Error};

use log;

/// 读取根目录中的文件
pub async fn file(mod_path: &str, url_path: &str) -> Result<Response<Body>, Error>
{
    //容错：去除'根'映射路径结尾的'/'（如果有），避免在路径中出现'//'
    let dir = remove_last_path(mod_path);
 
    let file_path_name = format!("{}{}", dir, url_path);
    Ok(read_file(file_path_name).await)
}

/// 读取模块路径中的文件
pub async fn module_file(mod_path: &str, url_path: &str, module_name: &str) -> Result<Response<Body>, Error>
{
    //容错：去除模块映射路径结尾的'/'（如果有），避免在路径中出现'//'
    let dir = remove_last_path(mod_path);
    //url_path去除module部分
    let remove_str = format!("/{}", module_name); 
    let url = &url_path[remove_str.len()..];
    //拼接本地路径和url
    let file_path_name = format!("{}{}", dir, url);
    Ok(read_file(file_path_name).await)
}

/// 读取默认html目录（安装路径）中的文件
pub async fn default_html_file(filename: &str) -> Result<Response<Body>, Error>
{
    let home = super::home_path();
    let home_path = Path::new(home);
    let default_html_path = home_path.join("html"); //默认html目录
    let file_path_name = default_html_path.join(filename);
    Ok(read_file(file_path_name.to_str().unwrap().to_string()).await)
}

/// 读文件 
///  file_path_name: 绝对路径
async fn read_file(file_path_name: String) -> Response<Body>
{
    //如果filename以'/'结尾，在后面补上index文件
    let mut filename = file_path_name;
    if filename.ends_with("/")
    {
        if let Some(filepath) = add_index(&filename)
        {
            filename = filepath;    //补上index文件
        }
    }
    log::debug!("[mod_html] read filename: {}", &filename);

    if let Ok(mut file) = File::open(&filename).await 
    {
        //判断是否超大文件
        let path = Path::new(&filename);
        if let Ok(metadata) = path.metadata()
        {
            //超大文件，按不定长chunked方式
            if metadata.len() > super::conf::chunked_size()
            {
                let stream = FramedRead::new(file, BytesCodec::new());
                let body = Body::wrap_stream(stream);
                return Response::new(body);
            }
        }

        //非超大文件
        let mut contents = vec![];  //文件内容
        if let Ok(_) = file.read_to_end(&mut contents).await    //读取文件
        {
            //根据扩展名定义Content-type
            let content_type = content_type(&filename);
            if let Ok(rsp) = Response::builder()
                                        .status(StatusCode::OK)
                                        .header(header::CONTENT_TYPE, content_type)
                                        .header(header::SERVER, super::VERSION)
                                        .body(Body::from(contents)) 
            {
                return rsp;
            }                  
        }
    }

    //文件读取错误
    super::response::rsp_404().await
}

/// 通过文件扩展名，匹配response的content_type
fn content_type(filename: &str) -> &str
{
    if let Some(ext_name) = extension(filename)     //获得文件后缀名
    {
        let ext_name = ext_name.to_uppercase();     //转大写
        match ext_name.as_str() 
        {
            "AAC"   => return "audio/aac",
            "AVI"   => return "video/x-msvideo",
            "BMP"   => return "image/bmp",
            "BZ"    => return "application/x-bzip",
            "BZ2"   => return "application/x-bzip2",
            "CSS"   => return "text/css",
            "CSV"   => return "text/csv",
            "DOC"   => return "application/msword",
            "DOCX"  => return "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "GIF"   => return "image/gif",
            "HTM"   => return "text/html",
            "HTML"  => return "text/html",
            "ICO"   => return "image/vnd.microsoft.icon",
            "JAR"   => return "application/java-archive",
            "JPEG"  => return "image/jpeg",
            "JPG"   => return "image/jpeg",
            "JS"    => return "text/javascript",
            "JSON"  => return "application/json",
            "MID"   => return "audio/midi",
            "MIDI"  => return "audio/midi",
            "MJS"   => return "text/javascript",     //JavaScript module
            "MP3"   => return "audio/mpeg",
            "MPEG"  => return "video/mpeg",
            "MPKG"  => return "application/vnd.apple.installer+xml",  //Apple Installer Package
            "ODP"   => return "application/vnd.oasis.opendocument.presentation", //OpenDocument presentation document
            "ODS"   => return "application/vnd.oasis.opendocument.spreadsheet", //OpenDocument spreadsheet document
            "ODT"   => return "application/vnd.oasis.opendocument.text", //OpenDocument text document
            "OGA"   => return "audio/ogg",
            "OGG"   => return "video/ogg",
            "OGV"   => return "video/ogg",
            "OGX"   => return "application/ogg",
            "OTF"   => return "font/otf",
            "PDF"   => return "application/pdf",
            "PNG"   => return "image/png",
            "PPT"   => return "application/vnd.ms-powerpoint",
            "PPTX"  => return "application/vnd.openxmlformats-officedocument.presentationml.presentation",
            "RAR"   => return "application/x-rar-compressed",
            "RTF"   => return "application/rtf",
            "SH"    => return "application/x-sh",
            "SVG"   => return "application/xml",
            "SWF"   => return "application/x-shockwave-flash",
            "TAR"   => return "application/x-tar",
            "TIF"   => return "image/tiff",
            "TIFF"  => return "image/tiff",
            "TTF"   => return "font/ttf",
            "TXT"   => return "text/plain",
            "VSD"   => return "application/vnd.visio",
            "WAV"   => return "audio/wav",
            "WEBA"  => return "audio/webm",
            "WEBM"  => return "video/webm",
            "WEBP"  => return "image/webp",
            "WOFF"  => return "font/woff",
            "WOFF2" => return "font/woff2",
            "XHTML" => return "application/xhtml+xml",
            "XLS"   => return "application/vnd.ms-excel",
            "XLSX"  => return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            "XML"   => return "text/xml",
            "XUL"   => return "application/vnd.mozilla.xul+xml",
            "ZIP"   => return "application/zip",
            "3GP"   => return "video/3gpp",
            "3G2"   => return "video/3gpp2",
            "7Z"    => return "application/x-7z-compressed",
            _       => return "application/octet-stream",
        }
    }

    "application/octet-stream"      //无扩展名
}

//获得文件扩展名
fn extension(filename: &str) -> Option<&str> 
{
    let mut has: bool = false;      //文件名中是否有'.'
    let mut dot = 0;                //句点index
    for (i, c) in filename.char_indices() 
    {
        if c == '.' 
        {
            has = true;
            dot = i + 1;
        }
    }

    if !has 
    {
        return None;    //没有扩展名
    }

    let extension = &filename[dot..];  
    Some(extension)
}

/// 返回path路径中的默认index文件（文件必须存在）
fn add_index(path: &str) -> Option<String>
{
    let arr = super::conf::index();
    for index in arr.iter()
    {
        let filename = format!("{}{}", path, index);
        let f = Path::new(&filename);
        if f.is_file()
        {
            return Some(filename);
        }
    }

    None
}

// 去除根路径和html模块配置路径中结尾的'/'（如果有）
fn remove_last_path(dir_path: &str) -> String
{
    let mut str = dir_path.to_string();
    if let Some('/') = str.pop()
    {
        return str;
    }

    dir_path.to_string()
}